import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';

import '../config.dart';
import 'logger.dart';

class Api {
  static final api = ApiRequest();

  static ApiRequest newInstance() {
    return ApiRequest();
  }

  static void addHeader(String key, String value) {
    api.addHeader(key, value);
  }

  static void removeHeader(String key) {
    api.removeHeader(key);
  }

  /// 会员已登录的状态下检查token是否需要刷新
  static Future<bool> _checkLoginState() async {
    /*if (app.isLogin) {
      return await app.login.refresh();
    }*/
    return true;
  }

  static Future<Result> get(String path, [Map<String, dynamic>? params]) async {
    await _checkLoginState();
    return await api.get(path, params);
  }

  static Future<Result> post(String path, [Map<String, dynamic>? data]) async {
    await _checkLoginState();
    return await api.post(path, data);
  }
}

class ApiRequest {
  final String apiServer;
  final String requestType;

  final options = BaseOptions(headers: {});

  ApiRequest._(this.apiServer, this.requestType) {
    if (!kIsWeb) {
      options.headers['User-Agent'] =
          "Mozilla/5.0 (${Platform.operatingSystem}/"
          "${Platform.operatingSystemVersion} "
          "Flutter/${Platform.version}) "
          "ShirneCMS/${Config.version} ";
    }
    options.headers['X-Requested-Type'] = requestType;
    options.headers['X-Requested-With'] = 'XMLHttpRequest';
    options.receiveDataWhenStatusError = true;
  }

  factory ApiRequest() {
    String apiServer = Config.server;
    String requestType = 'Api';
    // web环境无法设置请求头
    if (!kIsWeb) {
      if (Platform.isAndroid || Platform.isIOS) {
        requestType = 'App';
      } else {
        requestType = 'Desktop';
      }
    }

    if (!apiServer.endsWith('/')) {
      apiServer += '/';
    }

    return ApiRequest._(apiServer, requestType);
  }

  void addHeader(String key, String value) {
    options.headers[key] = value;
  }

  void removeHeader(String key) {
    if (options.headers.containsKey(key)) {
      options.headers.remove(key);
    }
  }

  String url(String path) {
    if (path.startsWith('http://') || path.startsWith('https://')) {
      return path;
    }
    return apiServer + path;
  }

  Future<Uint8List> getData(String path, [Map<String, dynamic>? params]) async {
    try {
      var response = await Dio(options).get(
        url(path),
        queryParameters: params,
        options: Options(responseType: ResponseType.stream),
      );
      final stream = await (response.data as ResponseBody).stream.toList();
      final result = BytesBuilder();
      for (Uint8List subList in stream) {
        result.add(subList);
      }
      return result.takeBytes();
    } on DioError catch (_) {
      rethrow;
    }
  }

  Future<Result> get(String path, [Map<String, dynamic>? params]) async {
    try {
      var response = await Dio(options).get<Map<String, dynamic>>(
        url(path),
        queryParameters: params,
      );
      return Result.response(response);
    } on DioError catch (_) {
      return Result.response(_.response!);
    }
  }

  Future<Result> post(String path, [Map<String, dynamic>? data]) async {
    try {
      final response = await Dio(options).post(
        url(path),
        data: data,
      );
      return Result.response(response);
    } on DioError catch (_) {
      if (_.response != null) {
        return Result.response(_.response!);
      }
      logger.warning('Request Error', _);
      return Result(500, _.message ?? 'Net error');
    }
  }
}

class Result {
  final int statusCode;
  int code = 0;
  String message = '';
  String? _body;
  Map<String, dynamic>? data;
  List<dynamic>? list;
  dynamic value;

  bool get needLogin => code == 99;

  Result.response(Response response) : statusCode = response.statusCode ?? 0 {
    if (statusCode == 200) {
      init(response.data);
    } else {
      message = response.statusMessage ?? 'Server error';
    }
  }

  Result(this.statusCode, String body) : _body = body {
    if (_body!.isNotEmpty) {
      try {
        var resultJson = json.decode(_body!);

        init(resultJson);
      } catch (e) {
        logger.severe('json decode error:', e);
      }
    }
  }

  init(resultJson) {
    if (resultJson != null && resultJson is Map<String, dynamic>) {
      code = resultJson['code'];
      message = resultJson['message'] ?? resultJson['msg'] ?? '';

      if (code == 1 && resultJson['data'] != null) {
        if (resultJson['data'] is Map) {
          if (resultJson['data'] is Map<String, dynamic>) {
            data = resultJson['data'];
          } else {
            data = resultJson['data'].map<String, dynamic>(
              (key, value) => MapEntry(key.toString(), value),
            );
          }
        } else if (resultJson['data'] is List) {
          list = resultJson['data'];
        } else {
          value = resultJson['data'];
          if (message.isEmpty) {
            message = value.toString();
          }
        }
      } else {
        logger.info(code);
        if (code == 102) {
          //app.login.clearLogin();
        }
      }
    }
  }

  @override
  String toString() => "Result:\n"
      "status:$statusCode\n"
      "code:$code\n"
      "message:$message\n"
      "data:$data";
}

class ServerException implements Exception {
  final String message;
  final int code;

  ServerException.fromResult(Result result)
      : message = result.message,
        code = result.code;
  ServerException(this.code, [this.message = '服务器繁忙']);

  @override
  String toString() => "ServerException:\n\n"
      "code:$code\n"
      "message:$message";
}
